import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;

/**
 * Constitutes the head class of an artifical neural network. This class
 * specifies how the training and classification is done in the network. It also
 * keeps track of each layer of neurons in the network.
 */
public class NeuralNet {
    // We finish training when TRAINING_TRESHHOLD is reached.
    private final double TRAINING_TRESHHOLD = 0.75;
    private final NeuronLayer outputNeurons;

    /**
     * Constructor
     * 
     * @param numInputs
     *            total number of input neurons
     * @param numOutputs
     *            total number of output neurons.
     */
    public NeuralNet(final int numInputs, final int numOutputs) {
        outputNeurons = new NeuronLayer(numOutputs, numInputs);
    }

    /**
     * Trains the neural network to recognize patterns in the pixelmaps. This
     * must be done prior to classifying pixelmaps since this is when the
     * network learns to classify them correctly. A subset of the training data
     * is used to verify whether the NN has reached the TRAINING_TRESHHOLD. The
     * training is done multiple times until the correct classification ratio is
     * at least TRAINING_TRESHHOLD. Furthermore the order of the trainingData is
     * randomized each iteration.
     * 
     * @param trainingData
     *            pixelmaps to train the network with.
     * @param trainingFacit
     *            correct answers to the pixelmaps in the trainingData
     */
    public void train(final LinkedList<PixelMapData> trainingData,
            final HashMap<String, Double> trainingFacit) {

        final int controlSetSize = (int) Math.floor(trainingData.size()
                * (1.0 / 4.0));

        final LinkedList<PixelMapData> ctrlSet = new LinkedList<PixelMapData>();

        for (int i = 0; i < controlSetSize; i++) {
            // move element into our controlset
            ctrlSet.add(trainingData.remove(i));
        }

        Double numRight;
        do {
            // Randomize order of training data.
            Collections.shuffle(trainingData);
            // train the neurons against this set of training data.
            for (final PixelMapData bmp : trainingData) {
                for (final Neuron output : outputNeurons.getNeurons()) {
                    output.train(bmp.getData(),
                            trainingFacit.get(bmp.getImageName()));
                }
            }
            numRight = 0.0;
            Double facit;
            for (final PixelMapData bmp : ctrlSet) {
                final double winnerId = classify(bmp.getData());
                facit = trainingFacit.get(bmp.getImageName());

                if (winnerId == facit) {
                    numRight++;
                }
            }
        } while (numRight / ctrlSet.size() < TRAINING_TRESHHOLD);
    }

    /**
     * Asks the neural network to classify a pixelmap. The id of whichever
     * output neuron that recognized the pixelmap the most, is returned as the
     * answer.
     * 
     * @param data
     *            the pixelmap to classify.
     * @return the id of the output neuron.
     */
    public double classify(final ArrayList<ArrayList<Double>> data) {

        final List<Neuron> neurons = outputNeurons.getNeurons();

        double retID = neurons.get(0).getId();
        double max = neurons.get(0).classify(data);

        for (int i = 1; i < neurons.size(); i++) {
            final double tmp = neurons.get(i).classify(data);

            if (tmp > max) {
                retID = neurons.get(i).getId();
                max = tmp;

            }
        }

        return retID;
    }
}
